package com.ccrfid.rmc6.service.business.rollcall2;

import com.ccrfid.rmc6.appconfig.exception.pojo.AppException;
import com.ccrfid.rmc6.appconfig.exception.pojo.CustomErrors;
import com.ccrfid.rmc6.entity.business.rollcall2.*;
import com.ccrfid.rmc6.entity.poi.PoiLocationEntity;
import com.ccrfid.rmc6.pojo.dto.rollcall2.RollCallPersonnelDto;
import com.ccrfid.rmc6.pojo.dto.rollcall2.RollCallSchedule;
import com.ccrfid.rmc6.pojo.dto.rollcall2.RollCallScheduleReload;
import com.ccrfid.rmc6.repository.business.rollcall2.*;
import com.ccrfid.rmc6.repository.poi.PoiLocationRepository;
import com.ccrfid.rmc6.repository.search.SearchCriteria;
import com.ccrfid.rmc6.service.amq.MqMsgProducer;
import com.ccrfid.rmc6.service.sys.OrgService;
import com.ccrfid.rmc6.util.DateUtil;
import com.ccrfid.rmc6.util.JsonUtil;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import org.hibernate.query.NativeQuery;
import org.hibernate.spatial.JTSGeometryType;
import org.hibernate.spatial.dialect.mysql.MySQLGeometryTypeDescriptor;
import org.hibernate.transform.Transformers;
import org.hibernate.type.IntegerType;
import org.hibernate.type.LongType;
import org.hibernate.type.StringType;
import org.hibernate.type.TimestampType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import javax.ws.rs.core.Response;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ScheduledFuture;

@Service
@Transactional
public class Rollcall2ServiceImpl implements Rollcall2Service {
    private static final Logger logger = LoggerFactory.getLogger(Rollcall2ServiceImpl.class);

    @PersistenceContext
    private EntityManager entityManager;
    @Autowired
    private RcOrgGroupRepository rcOrgGroupRepository;
    @Autowired
    private RcOrgGroupMemberRepository rcOrgGroupMemberRepository;
    @Autowired
    private RcLocationGroupRepository rcLocationGroupRepository;
    @Autowired
    private RcLocationGroupMemberRepository rcLocationGroupMemberRepository;
    @Autowired
    private RcRecordRepository rcRecordRepository;
    @Autowired
    private RcRecordDetailRepository rcRecordDetailRepository;
    @Autowired
    private PoiLocationRepository poiLocationRepository;
    @Autowired
    private OrgService orgService;
    @Autowired
    private RcScheduleRepository rcScheduleRepository;

    @Autowired
    private EventBus eventBus;
    @Autowired
    private TaskScheduler taskScheduler;
    @Autowired
    private MqMsgProducer mqMsgProducer;

    @Value("${rollcall.timeout}")
    private Integer rollcallTimeout;

    private List<ScheduledFuture> futureList = new ArrayList<>();

    @PostConstruct
    private void init() {
        //
        eventBus.register(this);
        //
        initRollCallScheduleTask();
    }

    //重置计划任务
    @Subscribe
    private void onRollCallScheduleReload(RollCallScheduleReload msg) {
        logger.debug("onRollCallScheduleReload ...");
        //
        cancelRollCallScheduleTask();
        //
        initRollCallScheduleTask();
    }

    //初始化点名计划任务
    private void initRollCallScheduleTask() {
        Date now = new Date();
        java.sql.Date today = new java.sql.Date(now.getTime());
        List<RcScheduleEntity> scheduleList = rcScheduleRepository.getAllBy();
        for (RcScheduleEntity scheduleEntity : scheduleList) {
            if (scheduleEntity.getEnabled().equals(1)) {
                Date startTime = DateUtil.mergeDateAndTime(today, scheduleEntity.getScheduleTime());
                if (startTime.before(now)) {
                    startTime = DateUtil.getOneDayLater(startTime);
                }
                //开启定时任务
                ScheduledFuture future = taskScheduler.scheduleAtFixedRate(new RollcallTask(scheduleEntity.getId()), startTime, 24 * 60 * 60 * 1000);
                futureList.add(future);
                logger.debug("add RollCallSchedule: {}， scheduleId: {}", future.hashCode(), scheduleEntity.getId());
            }
        }
    }

    private void cancelRollCallScheduleTask() {
        Iterator<ScheduledFuture> iterator = futureList.iterator();
        while (iterator.hasNext()) {
            ScheduledFuture future = iterator.next();
            if (future.cancel(true)) {
                iterator.remove();
                logger.debug("cancel RollCallSchedule return true, {}", future.hashCode());
            } else {
                logger.debug("cancel RollCallSchedule return false, {}", future.hashCode());
            }
        }
    }

    private class RollcallTask implements Runnable {
        private Long scheduleId;

        public RollcallTask(Long scheduleId) {
            this.scheduleId = scheduleId;
        }

        @Override
        public void run() {
            //计划任务开始点名
            mqMsgProducer.send2Topic("ws_rollcall_schedule",
                    JsonUtil.INSTANCE.writeValueAsString(new RollCallSchedule(scheduleId)));
        }
    }

    //点名计划任务消息
    @JmsListener(destination = "ws_rollcall_schedule", containerFactory = "topicListenerCF")
    public void onMqMsgScheduleRollcall(String content) {
        logger.debug("recv from topic[ws_rollcall_schedule]: {}", content);
        RollCallSchedule rollCallSchedule = JsonUtil.INSTANCE.readValue(content, RollCallSchedule.class);

        RcScheduleEntity rcScheduleEntity = rcScheduleRepository.getById(rollCallSchedule.getScheduleId());
        if (rcScheduleEntity != null) {
            try {
                doRollCall(rcScheduleEntity.getOrgGroupId(), rcScheduleEntity.getLocationGroupId(), 1);
            } catch (AppException ex) {
                logger.error("ex: {}, dbg: {}", ex.getMessage(), ex.getDebugMessage());
            }
        }
    }

    @Override
    public Page<RcRecordEntity> getRcRecordPage(SearchCriteria criteria, Pageable pageable) {
        return rcRecordRepository.search(criteria, pageable);
    }

    @Override
    public RcRecordEntity doRollCall(Long orgGroupId, Long locationGroupId, Integer submitStatus) throws AppException {
        RcOrgGroupEntity rcOrgGroupEntity = rcOrgGroupRepository.getById(orgGroupId);
        RcLocationGroupEntity rcLocationGroupEntity = rcLocationGroupRepository.getById(locationGroupId);
        if (rcOrgGroupEntity != null && rcLocationGroupEntity != null) {
            long timeNow = Instant.now().toEpochMilli();
            List<RollCallPersonnelDto> personnelList = getRcPersonnelList(orgGroupId);
            List<PoiLocationEntity> locationList = getLocationList(locationGroupId);
            List<RcRecordDetailEntity> detailEntityList = getRcRecordDetailList(timeNow, personnelList, locationList);

            int attendanceCount = 0;
            for (RcRecordDetailEntity detailEntity : detailEntityList) {
                if (detailEntity.getRollcallStatus().equals(1)) {
                    attendanceCount++;
                }
            }
            RcRecordEntity rcRecordEntity = new RcRecordEntity();
            rcRecordEntity.setRollcallTime(new Timestamp(timeNow));
            rcRecordEntity.setOrgGroupId(orgGroupId);
            rcRecordEntity.setOrgGroupName(rcOrgGroupEntity.getGroupName());
            rcRecordEntity.setLocationGroupId(locationGroupId);
            rcRecordEntity.setLocationGroupName(rcLocationGroupEntity.getGroupName());
            rcRecordEntity.setSubmitStatus(submitStatus);
            rcRecordEntity.setTotalCount(detailEntityList.size());
            rcRecordEntity.setAttendanceCount(attendanceCount);     //自动点到的在场人数
            rcRecordEntity.setAbsenceCount(0);                      //手动点到的不在场人数
            rcRecordEntity.setStatus(detailEntityList.size() == attendanceCount ? 0 : 1);  //0-正常；1-存在未点到人员
            rcRecordRepository.save(rcRecordEntity);

            for (RcRecordDetailEntity detailEntity : detailEntityList) {
                detailEntity.setRecordId(rcRecordEntity.getId());
                rcRecordDetailRepository.save(detailEntity);
            }
            return rcRecordEntity;
        } else {
            throw new AppException(Response.Status.BAD_REQUEST.getStatusCode(),
                    CustomErrors.org_or_location_is_null.getCode(), CustomErrors.org_or_location_is_null.getReason(), "");
        }
    }

    private List<RollCallPersonnelDto> getRcPersonnelList(Long orgGroupId) {
        String sql = "SELECT p.id as personnelId, p.personnel_name as personnelName, p.card_number as cardNumber, p.pos as pos, " +
                " p.floor_number as floorNumber, t.id as locationId, t.location_name as locationName, p.last_locate_time as locateTime, " +
                "p.org_Id as orgId,p.org_name as orgName,p.personnel_sn as personnelSn FROM v_personnel p " +
                " left join poi_location t on t.map_level = 3 and t.floor_number = p.floor_number and st_contains(t.location_polygon, p.pos) " +
                " where p.is_enabled = 1 and p.org_id in :orgIdList";
        Set<Long> orgIdList = new HashSet<>();
        List<RcOrgGroupMemberEntity> orgMemberList = rcOrgGroupMemberRepository.getAllByGroupId(orgGroupId);
        if (orgMemberList != null && orgMemberList.size() > 0) {
            for (RcOrgGroupMemberEntity memberEntity : orgMemberList) {
                orgIdList.addAll(orgService.getSubOrgIdList(memberEntity.getOrgId()));
            }
        }
        List<RollCallPersonnelDto> list = entityManager.createNativeQuery(sql)
                .setParameter("orgIdList", orgIdList)
                .unwrap(NativeQuery.class)
                .addScalar("personnelId", LongType.INSTANCE)
                .addScalar("personnelName", StringType.INSTANCE)
                .addScalar("cardNumber", LongType.INSTANCE)
                .addScalar("pos", new JTSGeometryType(MySQLGeometryTypeDescriptor.INSTANCE))
                .addScalar("floorNumber", IntegerType.INSTANCE)
                .addScalar("locationId", LongType.INSTANCE)
                .addScalar("locationName", StringType.INSTANCE)
                .addScalar("locateTime", TimestampType.INSTANCE)
                .addScalar("orgId", IntegerType.INSTANCE)
                .addScalar("orgName", StringType.INSTANCE)
                .addScalar("personnelSn", StringType.INSTANCE)
                .setResultTransformer(Transformers.aliasToBean(RollCallPersonnelDto.class))
                .getResultList();
        return list;
    }

    private boolean isAttended(RollCallPersonnelDto personnelDto, List<PoiLocationEntity> locationList) {
        for (PoiLocationEntity locationEntity : locationList) {
            if (locationEntity.getFloorNumber().equals(personnelDto.getFloorNumber())
                    && locationEntity.getLocationPolygon().contains(personnelDto.getPos())) {
                return true;
            }
        }
        return false;
    }

    private List<PoiLocationEntity> getLocationList(Long locationGroupId) {
        List<PoiLocationEntity> list = new ArrayList<>();
        List<RcLocationGroupMemberEntity> memberEntityList = rcLocationGroupMemberRepository.getAllByGroupId(locationGroupId);
        for (RcLocationGroupMemberEntity memberEntity : memberEntityList) {
            PoiLocationEntity locationEntity = poiLocationRepository.getById(memberEntity.getLocationId());
            if (locationEntity != null) {
                list.add(locationEntity);
            }
        }
        return list;
    }

    private List<RcRecordDetailEntity> getRcRecordDetailList(long timeNow,
                                                             List<RollCallPersonnelDto> personnelList,
                                                             List<PoiLocationEntity> locationList) {
        List<RcRecordDetailEntity> detailEntityList = new ArrayList<>();
        for (RollCallPersonnelDto personnelDto : personnelList) {
            RcRecordDetailEntity detailEntity = new RcRecordDetailEntity();
            detailEntity.setRollcallTime(new Timestamp(timeNow));
            detailEntity.setPersonnelId(personnelDto.getPersonnelId());
            detailEntity.setPersonnelName(personnelDto.getPersonnelName());
            detailEntity.setCardNumber(personnelDto.getCardNumber());
            detailEntity.setPos(personnelDto.getPos());
            detailEntity.setLocateTime(personnelDto.getLocateTime());
            detailEntity.setLocationId(personnelDto.getLocationId());
            detailEntity.setLocationName(personnelDto.getLocationName());
            detailEntity.setOrgId(personnelDto.getOrgId());
            detailEntity.setOrgName(personnelDto.getOrgName());
            detailEntity.setPersonnelSn(personnelDto.getPersonnelSn());


            if (personnelDto.getLocateTime() == null) {
                detailEntity.setRollcallStatus(0);
                detailEntity.setRemark("未被定位");
            } else {
                if (timeNow - personnelDto.getLocateTime().getTime() > rollcallTimeout) {
                    detailEntity.setRollcallStatus(0);
                    detailEntity.setRemark("点名定位超时");
                } else {
                    if (isAttended(personnelDto, locationList)) {
                        detailEntity.setRollcallStatus(1);
                        detailEntity.setRemark("点到");
                    } else {
                        detailEntity.setRollcallStatus(0);
                        detailEntity.setRemark("不在点名区域");
                    }
                }
            }
            detailEntityList.add(detailEntity);
        }
        return detailEntityList;
    }

    @Override
    public RcRecordEntity submitRcRecord(Long recordId) throws AppException {
        RcRecordEntity rcRecordEntity = rcRecordRepository.getById(recordId);
        if (rcRecordEntity != null) {
            rcRecordEntity.setSubmitStatus(1);
            return rcRecordRepository.save(rcRecordEntity);
        } else {
            throw new AppException(Response.Status.NOT_FOUND.getStatusCode(),
                    CustomErrors.resource_not_found.getCode(), CustomErrors.resource_not_found.getReason(), "");
        }
    }

    @Override
    public Page<RcRecordDetailEntity> getRcRecordDetailPage(SearchCriteria criteria, Pageable pageable) {
        return rcRecordDetailRepository.search(criteria, pageable);
    }

    @Override
    public RcRecordDetailEntity updateRcRecordDetail(Long detailId, RcRecordDetailEntity entity) throws AppException {
        RcRecordDetailEntity rcRecordDetailEntity = rcRecordDetailRepository.getById(detailId);
        if (rcRecordDetailEntity != null) {
            //1.更新某个人的点名状态
            rcRecordDetailEntity.setRollcallStatus(entity.getRollcallStatus()); //0-未点到；1-自动点到；2-手动点到
            rcRecordDetailEntity.setRemark(entity.getRemark());
            rcRecordDetailRepository.save(rcRecordDetailEntity);

            //2.更新点名记录状态
            Long recordId = rcRecordDetailEntity.getRecordId();
            RcRecordEntity rcRecordEntity = rcRecordRepository.getById(recordId);
            if (rcRecordEntity != null) {
                int attendanceCount = 0;
                int absenceCount = 0;
                List<RcRecordDetailEntity> detailEntityList = rcRecordDetailRepository.getAllByRecordId(recordId);
                for (RcRecordDetailEntity detailEntity : detailEntityList) {
                    if (detailEntity.getRollcallStatus().equals(1)) {
                        attendanceCount++;
                    } else if (detailEntity.getRollcallStatus().equals(2)) {
                        absenceCount++;
                    }
                }
                rcRecordEntity.setAttendanceCount(attendanceCount);
                rcRecordEntity.setAbsenceCount(absenceCount);
                if (detailEntityList.size() > (attendanceCount + absenceCount)) {
                    rcRecordEntity.setStatus(1);
                } else {
                    rcRecordEntity.setStatus(0);
                }
                rcRecordRepository.save(rcRecordEntity);
            }
            return rcRecordDetailEntity;
        } else {
            throw new AppException(Response.Status.NOT_FOUND.getStatusCode(),
                    CustomErrors.resource_not_found.getCode(), CustomErrors.resource_not_found.getReason(), "");
        }
    }

    @Override
    public List<Long> getGroupIdByOrgId(Long orgId){
        List<RcOrgGroupMemberEntity> entityList = rcOrgGroupMemberRepository.getAllByOrgId(orgId);
        List<Long> groupIdLIst = new ArrayList<>();
        for(RcOrgGroupMemberEntity entity : entityList){
            groupIdLIst.add(entity.getGroupId());
        }
        return groupIdLIst;
    }
}
